// Copyright (c) 2024 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.


#pragma once

/**
 * @brief   包含了适用于STM32F1 系列的GPIO 操作
 * 
 * STM32F1 的HAL 库在在LL_GPIO_PIN_N 的定义上做了些特殊处理，详细的参考：
 */


#include "common_cm_pin.hpp"
#include "ioxx_common.hpp"
#include "ioxx_include_device.hpp"  // 引入CMSIS 器件头文件，可能需要用户定义的配置文件

#ifndef _IOXX_GPIO_ALTERNATE_SELECTOR_AVAILABLE
    #ifdef LL_GPIO_AF_0

        #define _IOXX_GPIO_ALTERNATE_SELECTOR_AVAILABLE

    #endif
#endif


// 时钟

#define _IOXX_GPIO_ENABLE_CLOCK  _SUPER_CAT3(LL_, _IOXX_GPIO_BUS, _EnableClock)
#define _IOXX_GPIO_DISABLE_CLOCK _SUPER_CAT3(LL_, _IOXX_GPIO_BUS, _DisableClock)


namespace ioxx {

    /**
     * @brief 打开所有GPIO 端口的时钟，AFIO 的时钟也会被打开
     *
     * IOXX 的目标是尽量提供接近Arduino 的开发效率，所以不直接提供太细粒度的控制，
     * 如果在低功耗场合需要精细的考虑外设的时钟，那就使用更底层的工具
     */
    inline void enable_gpio() {
        _IOXX_GPIO_ENABLE_CLOCK(_IOXX_GPIO_ALL_BUS_MASK);
    }

    inline void disable_gpio() {
        _IOXX_GPIO_DISABLE_CLOCK(_IOXX_GPIO_ALL_BUS_MASK);
    }

    // inline void enable_gpio(GPIO_TypeDef *port) {

    // }

    // inline void disable_gpio(GPIO_TypeDef *port) {
    // }
}  // namespace ioxx


// 模式

namespace ioxx {

    enum class mode {
        in = 0b0100,  // 从高到低依次表示CNF0[1],CNF0[0],MODE0[1],MODE0[0]
        out = 0b0000,
        alt = 0b1000,

        analog = 0xff,  // analog 必须定义成这样，不然会跟out 的值重复
    };

    constexpr uint32_t _PIN_MODE_MARK_IN_PULL = 0b1000;


    enum class drive {
        push_pull = 0b0000,
        open_drain = 0b0100,
    };


    enum class pull {
        up = 1,
        down = 0,

        none = -1,
    };


    enum class speed {
        low = 0b0010,
        medium = 0b0001,
        high = 0b0011,

        very_high = high,

        lowest = low,
        highest = very_high,

        normal = medium,
    };


    // template <typename PinType = _DefaultPinType>
    class PinInit {
       public:
        using PinType = Pin;

       private:
        // 引脚默认配置：输入模式、输出频率最高、推挽输出、无上下拉
        // 上下拉电阻在输出模式下也会生效，会产生不必要的电流，所以默认关闭上下拉
        LL_GPIO_InitTypeDef _param{
            .Pin = 0,
            .Mode = _enum_to_underlying(mode::in),
            .Speed = _enum_to_underlying(speed::normal),
            .OutputType = _enum_to_underlying(drive::push_pull),
            .Pull = static_cast<uint32_t>(_enum_to_underlying(pull::none)),

#ifdef _IOXX_GPIO_AF_SELECTION_AVAILABLE
            .Alternate = 0,
#endif
        };

       public:
        PinInit &mode(ioxx::mode m) {
            _param.Mode = _enum_to_underlying(m);
            return *this;
        }

        PinInit &speed(ioxx::speed s) {
            _param.Speed = _enum_to_underlying(s);
            return *this;
        }

        PinInit &drive(ioxx::drive d) {
            _param.OutputType = _enum_to_underlying(d);
            return *this;
        }

        PinInit &pull(ioxx::pull p) {
            _param.Pull = _enum_to_underlying(p);
            return *this;
        }

#ifdef _IOXX_GPIO_AF_SELECTION_AVAILABLE

        PinInit &af(uint32_t af_select) {
            _param.Alternate = af_select;
            return *this;
        }

#endif

        PinInit &init(const PinType &pin) {
            // 官方的LL 库的GPIO_PIN 定义比较特殊，与一般的pin_mask 不兼容
            // 总之是自己实现了GPIO 初始化

            uint32_t config = _param.Mode;
            int pl = static_cast<int>(_param.Pull);

            if (config == _enum_to_underlying(mode::analog)) {
                config = 0;
            }
            else if (config == _enum_to_underlying(mode::in) && pl >= 0) {
                config = _PIN_MODE_MARK_IN_PULL;
            }
            else {
                // out 或alt 模式，加上速度和输出类型标记
                config += _param.Speed + _param.OutputType;
            }


            unsigned pos = pin.pin_num();
            volatile uint32_t *reg;
            if (pos > 7) {
                reg = &(pin.port()->CRH);
                pos = (pos - 8) << 2;  // 左移2 == 乘4
            }
            else {
                reg = &(pin.port()->CRL);
                pos = pos << 2;
            }

            uint32_t tmp = *reg;
            uint32_t clr_mask = 0xf << pos;
            clr_mask = ~clr_mask;  // 用于清除4 个bit 的mask

            tmp &= clr_mask;
            tmp |= config << pos;
            *reg = tmp;

            if (config == _PIN_MODE_MARK_IN_PULL) {
                // 设置上 / 下拉，会修改ODR 寄存器
                pin.set(pl);
            }

            return *this;
        }
    };
}  // namespace ioxx